/**
 * FileSet.java
 * @author: liuex
 * 2007-12-27 ����09:36:20
 *
 * note: 
 */
package lex.mtd.core;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;

import org.lex.io.filesystem.FileNameUtilities;


public class FileBlockSet extends SimpleContentLengthVerifier implements Serializable,
		ContentLengthVerifier
{
	private static final long serialVersionUID = 5701480850374950862L;
	private static final boolean DEBUG = true;
	// directory of downloaded file
	private File directory = null;
	// target file's name
	private String basename = null;
	// target file's extension name, maybe empty, but never null
	private String extname = null;
	private List<FileBlock> fileSet = null;
	private long minimalDataBlock = DownloadManagerConfig.DEFAULT.minimalDataBlock;
	private AtomicInteger idgen = new AtomicInteger( 1 );
	// =============
	boolean isFinished = false;// used in merge

	public FileBlockSet( File directory, String filename, long minimalDataBlock )
	{
		if( false == directory.exists() || false == directory.isDirectory() )
			directory.mkdirs();
		this.directory = directory;
		this.basename = FileNameUtilities.getBaseName( filename );
		this.extname = FileNameUtilities.getExtension( filename );
		this.fileSet = new Vector<FileBlock>();
		this.minimalDataBlock = minimalDataBlock;
	}

	public File getDirectory()
	{
		return directory;
	}

	public String getBasename()
	{
		return basename;
	}

	public String getExtname()
	{
		return extname;
	}

	public String getFileName()
	{
		return this.basename + "." + this.extname;
	}

	public boolean verifyContentLength( long length )
	{
		if( -1L == this.contentLength )
		{
			this.contentLength = length;
			return true;
		}
		else if( this.contentLength != length )
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	public synchronized int getFileCount()
	{
		return this.fileSet.size();
	}

	public synchronized FileBlock getFileBlock( int index )
	{
		return fileSet.get( index );
	}

	// used in FilePartSetMergeUtils
	synchronized void remove( FileBlock part )
	{
		this.fileSet.remove( part );
		dump();
	}

	public synchronized void merge() throws IOException
	{
		FileBlockSetMergeUtils.mergeFiles( this );
	}

	public synchronized boolean isFinished()
	{
		if( true == this.isFinished )
			return true;
		for( int i = 0; i < fileSet.size(); i++ )
			if( false == fileSet.get( i ).isFinished() )
				return false;
		return true;
	}

	public synchronized long getTransferredBytes()
	{
		long len = 0;
		for( int i = 0; i < fileSet.size(); i++ )
			len += fileSet.get( i ).getOffset();
		return len;
	}

	public long getMaxRemainLength()
	{
		if( 0 == fileSet.size() )
			return this.contentLength;
		else
			return Collections.max( fileSet, FileBlock.REMAIN_LENGTH_COMPARATOR ).getRemainLength();
	}

	/**
	 * ����ʣ���С�����ļ���
	 * 
	 * @return ������еĿ��ʣ���С����<code>minimalDataBlock</code>С������null
	 */
	public synchronized FileBlock splitMaxRemainLengthBlock()
	{
		if( this.isFinished )
			return null;
		else if( 0 == fileSet.size() )
		{
			if( -1 == this.contentLength )
				return null;
			else
				return this.createPart( 0, this.getContentLength() );
		}
		else
		{
			FileBlock part = Collections.max( fileSet, FileBlock.REMAIN_LENGTH_COMPARATOR );
			if( part.getRemainLength() > this.minimalDataBlock )
				return this.split( part );
			else
				return null;
		}
	}

	/**
	 * ��ָ�����ļ�����ѳ�2���֣�ʹ��ÿһ�����ʣ���С�����
	 * 
	 * @param part
	 * @return
	 */
	private FileBlock split( FileBlock part )
	{
		synchronized( part )
		{
			long remain = part.getRemainLength();
			long newRemain = remain / 2;
			long end = part.end;
			long start = end - newRemain;
			part.end = start;
			FileBlock newPart = this.createPart( start, end );
			return newPart;
		}
	}

	/**
	 * create && add
	 * 
	 * @param start
	 * @param end
	 * @return
	 */
	private FileBlock createPart( long start, long end )
	{
		int id = this.idgen.getAndIncrement();
		FileBlock part = new FileBlock( id, this.directory, this.getBasename(), this.getExtname() );
		part.start = start;
		part.end = end;
		this.fileSet.add( part );
		Collections.sort( fileSet, FileBlock.START_POSITION_COMPARATOR );
		dump();
		return part;
	}

	void dump()
	{
		if( DEBUG )
		{
			if( 0 == fileSet.size() )
				return;
			char[] sep = new char[80];
			Arrays.fill( sep, '-' );
			System.out.println( sep );
			System.out.println( this.getFileName() + " dump:" );
			System.out.println( "Content Length  : " + this.contentLength );
			System.out.println( "File Block Count: " + this.fileSet.size() );
			for( int i = 0; i < fileSet.size(); i++ )
			{
				System.out.println( fileSet.get( i ) );
				System.out.println();
			}
		}
	}

	public String toString()
	{
		StringBuilder buf = new StringBuilder( "FileSet has " );
		buf.append( fileSet.size() ).append( " files: " );
		for( int i = 0; i < fileSet.size(); i++ )
		{
			buf.append( fileSet.get( i ).getFile().getName() );
			if( i < fileSet.size() - 1 )
				buf.append( ", " );
		}
		return buf.toString();
	}
}
